/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package util;

import java.util.ArrayList;
import java.util.Collections;
import mopso.Particle;
import mopso.ParticleData;
import mopso.Particle_CSS;
import mopso.Problem;

/**
 *
 * @author robson
 */
public class Diversity {

    public ArrayList<Particle> calculateCrowdingDistance( ArrayList<Particle> externalArchive, Problem problem ) {

        for (int j = 0; j < externalArchive.size(); j++) {
            externalArchive.get(j).getParticleData().setCrowdingDistance(0.0);
        }

        for (int i = 0; i < problem.getNObjectives(); i++) {
            for (int j = 0; j < externalArchive.size(); j++) {
                externalArchive.get(j).getParticleData().setSortType(ParticleData.Sorting.SORTBYFITNESS);
                externalArchive.get(j).getParticleData().setComparableDimension(i);
            }

            Collections.sort(externalArchive);

            for (int j = 0; j < externalArchive.size(); j++) {
                externalArchive.get(j).getParticleData().setCrowdingDistance(this.calculateCrowdingDistanceByDimension(externalArchive.get(j), externalArchive, i, j));
            }
        }

        for (int j = 0; j < externalArchive.size(); j++) {
            externalArchive.get(j).getParticleData().setSortType(ParticleData.Sorting.SORTBYCROWDINGDISTANCE);
        }

        Collections.sort(externalArchive, Collections.reverseOrder());
        
        return externalArchive;
    }
    
    public ArrayList<Particle_CSS> calculateCrowdingDistance_CSS( ArrayList<Particle_CSS> externalArchive, Problem problem ) {

        for (int j = 0; j < externalArchive.size(); j++) {
            externalArchive.get(j).getParticleData().setCrowdingDistance(0.0);
        }

        for (int i = 0; i < problem.getNObjectives(); i++) {
            for (int j = 0; j < externalArchive.size(); j++) {
                externalArchive.get(j).getParticleData().setSortType(ParticleData.Sorting.SORTBYFITNESS);
                externalArchive.get(j).getParticleData().setComparableDimension(i);
            }

            Collections.sort(externalArchive);

            for (int j = 0; j < externalArchive.size(); j++) {
                externalArchive.get(j).getParticleData().setCrowdingDistance(this.calculateCrowdingDistanceByDimension(externalArchive.get(j), externalArchive, i, j));
            }
        }

        for (int j = 0; j < externalArchive.size(); j++) {
            externalArchive.get(j).getParticleData().setSortType(ParticleData.Sorting.SORTBYCROWDINGDISTANCE);
        }

        Collections.sort(externalArchive, Collections.reverseOrder());
        
        return externalArchive;
    }    

    private double calculateCrowdingDistanceByDimension(Particle particle, ArrayList<Particle> externalArchive, int dimension, int index) {
        double crowdingDistance = particle.getParticleData().getCrowdingDistance();

        double valueMax = this.getDimensionMaxValue(externalArchive, dimension);
        double valueMin = this.getDimensionMinValue(externalArchive, dimension);
        double delta = Math.abs(valueMax - valueMin);

        if ((externalArchive.size() >= 2) && (delta != 0.0)) {

            if (index == 0) {
                crowdingDistance += (Math.abs(externalArchive.get(index).getParticleData().getFitness()[dimension] - externalArchive.get(index + 1).getParticleData().getFitness()[dimension]) +
                        Math.abs(externalArchive.get(externalArchive.size() - 1).getParticleData().getFitness()[dimension] - externalArchive.get(index).getParticleData().getFitness()[dimension])) /
                        (delta);

            // crowding distance original
            // crowdingDistance = Double.MAX_VALUE;

            } else if (index == (externalArchive.size() - 1)) {
                crowdingDistance += (Math.abs(externalArchive.get(index).getParticleData().getFitness()[dimension] - externalArchive.get(0).getParticleData().getFitness()[dimension]) +
                        Math.abs(externalArchive.get(index - 1).getParticleData().getFitness()[dimension] - externalArchive.get(index).getParticleData().getFitness()[dimension])) /
                        (delta);

            // crowding distance original
            // crowdingDistance = Double.MAX_VALUE;

            } else {
                crowdingDistance += (Math.abs(externalArchive.get(index).getParticleData().getFitness()[dimension] - externalArchive.get(index + 1).getParticleData().getFitness()[dimension]) +
                        Math.abs(externalArchive.get(index - 1).getParticleData().getFitness()[dimension] - externalArchive.get(index).getParticleData().getFitness()[dimension])) /
                        (delta);
            }

        } else {
            crowdingDistance = 0.0;
        }

        return crowdingDistance;
    }
    
    private double calculateCrowdingDistanceByDimension(Particle_CSS particle_CSS, ArrayList<Particle_CSS> externalArchive, int dimension, int index) {
        double crowdingDistance = particle_CSS.getParticleData().getCrowdingDistance();

        double valueMax = this.getDimensionMaxValue_CSS(externalArchive, dimension);
        double valueMin = this.getDimensionMinValue_CSS(externalArchive, dimension);
        double delta = Math.abs(valueMax - valueMin);

        if ((externalArchive.size() >= 2) && (delta != 0.0)) {

            if (index == 0) {
                crowdingDistance += (Math.abs(externalArchive.get(index).getParticleData().getFitness()[dimension] - externalArchive.get(index + 1).getParticleData().getFitness()[dimension]) +
                        Math.abs(externalArchive.get(externalArchive.size() - 1).getParticleData().getFitness()[dimension] - externalArchive.get(index).getParticleData().getFitness()[dimension])) /
                        (delta);

            // crowding distance original
            // crowdingDistance = Double.MAX_VALUE;

            } else if (index == (externalArchive.size() - 1)) {
                crowdingDistance += (Math.abs(externalArchive.get(index).getParticleData().getFitness()[dimension] - externalArchive.get(0).getParticleData().getFitness()[dimension]) +
                        Math.abs(externalArchive.get(index - 1).getParticleData().getFitness()[dimension] - externalArchive.get(index).getParticleData().getFitness()[dimension])) /
                        (delta);

            // crowding distance original
            // crowdingDistance = Double.MAX_VALUE;

            } else {
                crowdingDistance += (Math.abs(externalArchive.get(index).getParticleData().getFitness()[dimension] - externalArchive.get(index + 1).getParticleData().getFitness()[dimension]) +
                        Math.abs(externalArchive.get(index - 1).getParticleData().getFitness()[dimension] - externalArchive.get(index).getParticleData().getFitness()[dimension])) /
                        (delta);
            }

        } else {
            crowdingDistance = 0.0;
        }

        return crowdingDistance;
    }

    public double getDimensionMaxValue(ArrayList<Particle> externalArchive, int dimension) {
        double maxValue = Double.MIN_VALUE;

        for (int j = 0; j < externalArchive.size(); j++) {
            if (externalArchive.get(j).getParticleData().getFitness()[dimension] > maxValue) {
                maxValue = externalArchive.get(j).getParticleData().getFitness()[dimension];
            }
        }

        return maxValue;
    }

    public double getDimensionMinValue(ArrayList<Particle> externalArchive, int dimension) {
        double minValue = Double.MAX_VALUE;

        for (int j = 0; j < externalArchive.size(); j++) {
            if (externalArchive.get(j).getParticleData().getFitness()[dimension] < minValue) {
                minValue = externalArchive.get(j).getParticleData().getFitness()[dimension];
            }
        }

        return minValue;
    }
    
    public double getDimensionMaxValue_CSS(ArrayList<Particle_CSS> externalArchive, int dimension) {
        double maxValue = Double.MIN_VALUE;

        for (int j = 0; j < externalArchive.size(); j++) {
            if (externalArchive.get(j).getParticleData().getFitness()[dimension] > maxValue) {
                maxValue = externalArchive.get(j).getParticleData().getFitness()[dimension];
            }
        }

        return maxValue;
    }

    public double getDimensionMinValue_CSS(ArrayList<Particle_CSS> externalArchive, int dimension) {
        double minValue = Double.MAX_VALUE;

        for (int j = 0; j < externalArchive.size(); j++) {
            if (externalArchive.get(j).getParticleData().getFitness()[dimension] < minValue) {
                minValue = externalArchive.get(j).getParticleData().getFitness()[dimension];
            }
        }

        return minValue;
    }

    public ArrayList<ParticleData> calculateCrowdingDistanceParticleData(ArrayList<ParticleData> pbests, Problem problem) {

        for (int j = 0; j < pbests.size(); j++) {
            pbests.get(j).setCrowdingDistance(0.0);
        }

        for (int i = 0; i < problem.getNObjectives(); i++) {
            for (int j = 0; j < pbests.size(); j++) {
                pbests.get(j).setSortType(ParticleData.Sorting.SORTBYFITNESS);
                pbests.get(j).setComparableDimension(i);
            }

//            System.out.println("Arquivo externo antes de ordenar");
//            this.printParticles(externalArchive);

            Collections.sort(pbests);

//            System.out.println("Arquivo externo apos ordenar");
//            this.printParticles(externalArchive);

            for (int j = 0; j < pbests.size(); j++) {
                pbests.get(j).setCrowdingDistance(this.calculateCrowdingDistanceByDimensionParticleData(pbests.get(j), pbests, i, j));
            }
        }

        for (int j = 0; j < pbests.size(); j++) {
            pbests.get(j).setSortType(ParticleData.Sorting.SORTBYCROWDINGDISTANCE);
        }

        Collections.sort(pbests, Collections.reverseOrder());

//        System.out.println("Arquivo externo apos ordenar por Crowding Distance");
//        this.printParticles(externalArchive);
        return pbests;

    }

    private double calculateCrowdingDistanceByDimensionParticleData(ParticleData particleData, ArrayList<ParticleData> pbests, int dimension, int index) {
        double crowdingDistance = particleData.getCrowdingDistance();

        double valueMax = this.getDimensionMaxValueParticleData(pbests, dimension);
        double valueMin = this.getDimensionMinValueParticleData(pbests, dimension);
        double delta = Math.abs(valueMax - valueMin);

        if ((pbests.size() >= 2) && (delta != 0.0)) {

            if (index == 0) {
                crowdingDistance += (Math.abs(pbests.get(index).getFitness()[dimension] - pbests.get(index + 1).getFitness()[dimension]) +
                        Math.abs(pbests.get(pbests.size() - 1).getFitness()[dimension] - pbests.get(index).getFitness()[dimension])) /
                        (Math.abs(delta));

            // crowding distance original
            // crowdingDistance = Double.MAX_VALUE;

            } else if (index == (pbests.size() - 1)) {
                crowdingDistance += (Math.abs(pbests.get(index).getFitness()[dimension] - pbests.get(0).getFitness()[dimension]) +
                        Math.abs(pbests.get(index - 1).getFitness()[dimension] - pbests.get(index).getFitness()[dimension])) /
                        (Math.abs(delta));

            // crowding distance original
            // crowdingDistance = Double.MAX_VALUE;

            } else {
                crowdingDistance += (Math.abs(pbests.get(index).getFitness()[dimension] - pbests.get(index + 1).getFitness()[dimension]) +
                        Math.abs(pbests.get(index - 1).getFitness()[dimension] - pbests.get(index).getFitness()[dimension])) /
                        (Math.abs(delta));
            }

        } else {
            crowdingDistance = 0.0;
        }

        return crowdingDistance;
    }

    private double getDimensionMaxValueParticleData(ArrayList<ParticleData> pbests, int dimension) {
        double maxValue = 0.0;

        for (int j = 0; j < pbests.size(); j++) {
            if (pbests.get(j).getFitness()[dimension] > maxValue) {
                maxValue = pbests.get(j).getFitness()[dimension];
            }
        }

        return maxValue;
    }

    private double getDimensionMinValueParticleData(ArrayList<ParticleData> pbests, int dimension) {
        double minValue = Double.MAX_VALUE;

        for (int j = 0; j < pbests.size(); j++) {
            if (pbests.get(j).getFitness()[dimension] < minValue) {
                minValue = pbests.get(j).getFitness()[dimension];
            }
        }

        return minValue;
    }


}
